home *** CD-ROM | disk | FTP | other *** search
/ Aminet 3 / Aminet 3 - July 1994.iso / Aminet / disk / cache / SmartDiskZeus.lha / dl / SmartDisk131 / SmartDisk.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-01-19  |  15.8 KB  |  635 lines

  1. /*
  2.  * SmartDisk 1.3.1
  3.  *
  4.  * Created by David Le Blanc 29/10/91 Absolutely no copywrite. But if you improve
  5.  * it, please send me a new version (with source!) 
  6.  * 
  7.  * Current limitations.
  8.  *   No command line options, device and cache size are hard-wired into the program.
  9.  *   Only one unit is supported.
  10.  *
  11.  * Obvious enhacements.
  12.  *   Make device and unit configurable, as well as cache size and shape.
  13.  *
  14.  * Some performance quotes: (Doesn't everyone make these??)
  15.  * Background:  I have a directory called MAN: which has 355 manuals.
  16.  *
  17.  * WARNING: These are bad examples, since a 'dir' reads the disk, then sorts the
  18.  * contents, then writes the data to the screen. These times include the sorting
  19.  * and output of the directory. This sorting and output time is in the order
  20.  * of 1.5 to 2 seconds.
  21.  *
  22.  * Normal DIR MAN:        12 seconds
  23.  * Cache enabled but empty      9  seconds (prefetch does work!) 
  24.  * Cache primed                 5  seconds.
  25.  *
  26.  * With a slower drive and/or faster machine these times can only improve.
  27.  * I have a drive capable of 800k/sec on my unaccelerated A500. Those with
  28.  * a 150K/sec A590 would notice a greater performance boost. Same for those
  29.  * with 'bloody fast machines' (grumble :) If I was REALLY worried about making
  30.  * the statistic look good, then I'd test it on an A590.
  31.  *
  32.  * A590 USERS: You MUST edit the 'scsi.device' to 'xt.device' or it will NOT WORK.
  33.  * GVP  USERS: You MUST edit the 'scsi.device' to 'gvpscsi.device' or it will
  34.  *             NOT WORK.
  35.  *
  36.  * SmartDisk: A SCSI.DEVICE disk cache.
  37.  *
  38.  *              Code has been fixed to work with PPS Zeus '040
  39.  *
  40.  * 1.3.1:       Removed some 2.0ism's that crept in, and added code to
  41.  *              read the device and unit from the command line.
  42.  * 1.3:        Added Prefetch and fixed 'CMD_WRITE' to update instead of
  43.  *              flush cache entry.
  44.  * 1.2:         Added many optimisation such as moving the semaphore locks to
  45.  *              the update functions, and for a request of many blocks, the
  46.  *              blocks are read in all at once into the users buffer, THEN
  47.  *              copied to cache. (Instead of read into cache one at a time and
  48.  *              then copied to the user buffer)
  49.  * 1.1:        Found a major problem. The following union under Lattice 5.10a
  50.  *              generates unexpected (probably correct) code. The key, line and
  51.  *        offset fields are actually unioned together, whereas I though
  52.  *        the union would be between the two int declarations. 
  53.  *
  54.  *              union sector {
  55.  *                 int key:32-LINE_BITS-ITEM_BITS,
  56.  *                     line:LINE_BITS,
  57.  *                     offset:ITEM_BITS ;
  58.  *                 int sector ;
  59.  *                 } ;
  60.  *
  61.  *        I made the bit fields a separate structure, and union'ed the 
  62.  *          sector and the new structure together.
  63.  *
  64.  * 1.0:        My school assigment was to write a N-way set associative cache
  65.  *        simulator and to plot hit-rate percentages. With a bit of
  66.  *        work, it is not a Hard Disk cache! Vive la Amiga!
  67.  *
  68.  *
  69.  * History:
  70.  *    29/10/91 : Sat down an wrote a cache.
  71.  *    30/10/91 : Problem? Works Great Now.
  72.  *    31/10/91 : Fixed up the 'aging' of blocks to handle the
  73.  *             : counter wrapping after 2^32 buffer creates/updates.
  74.  *             : (How long does 2^32 DIFFERENT accesses take?? With a fast hard
  75.  *                disk going continually, I estimate about two months.)
  76.  *             : Cleaned up the code a tad.
  77.  */
  78. #include <stdio.h>
  79. #include <stdlib.h>
  80. #include <math.h>
  81. #include <m68881.h>
  82. #include <exec/types.h>
  83. #include <exec/memory.h>
  84. #include <proto/all.h>
  85.  
  86. struct IOStdReq *IO;        /* All IO goes through here now. */
  87. struct SignalSemaphore *ss ;    /* To force single threading     */
  88.                 /* through the code responsible     */
  89.                 /* for updating the cache     */
  90. ULONG counter,reads=0,hits=0,misses=0;
  91. LONG error;
  92.  
  93. int allocnum = 0 ;
  94.  
  95. /*
  96.  * Constants. Adjust for adjusting cache size and shape.
  97.  */
  98.  
  99. #define ITEM_SIZE 512           /* No change - sector size */
  100.  
  101. #define LINE_SIZE   4           /* Increase for prefetch */
  102. #define SETS        8           /* N-way set associative */
  103. #define LINES      32           /* N lines of cache      */
  104.  
  105. /*
  106.  * Bit fields. Adjust to match above constants.
  107.  */
  108.  
  109. #define ITEM_BITS  1            /* 2^ITEM_BITS = LINE_SIZE */
  110. #define LINE_BITS  7            /* 2^LINE_BITS = LINES     */
  111.  
  112. struct bitaddress {
  113.    int key:32-LINE_BITS-ITEM_BITS,
  114.        line:LINE_BITS,
  115.        offset:ITEM_BITS ;
  116.    } ;
  117.  
  118. union sector {
  119.    int sector ;
  120.    struct bitaddress s ;
  121.    } ;
  122.  
  123. struct cache_line {
  124.    int key ;                     /* Item key */
  125.    int age ;                     /* AGE for LRU alg */
  126.    int valid ;
  127.    char *buffer ;                /* [LINE_SIZE][ITEM_SIZE] of DATA */
  128.    } ;
  129.  
  130. struct cache_line cache[SETS][LINES] ;
  131.  
  132. /*
  133.  * A buffer for scsidisk.device to go through.
  134.  */
  135. char __chip globbuffer[LINE_SIZE << 9] ;
  136.  
  137. #define DEV_BEGINIO (-30)
  138. #define DEV_ABORTIO (-36)
  139.  
  140. void (*__asm oldbeginio)(register __a1 struct IOStdReq *,
  141.                          register __a6 struct Device *dev) ;
  142. /*
  143.  * Scan for sector, and return the set it resides in.
  144.  */
  145. int
  146. FindEntry(union sector *s) {
  147.  
  148.    int set ;
  149.  
  150.    for (set = 0; set < SETS; set++) 
  151.       if (cache[set][s->s.line].valid) {
  152.          if (cache[set][s->s.line].key == s->s.key) {
  153.             cache[set][s->s.line].age = counter ++ ;
  154.             return set ;
  155.             }
  156.          }
  157.       else
  158.          break ;
  159.  
  160.    return -1 ;
  161.    }
  162.  
  163. /*
  164.  * Pick a set from the associated cache, and return
  165.  * the set number. The cache memory is also allocated
  166.  * before returning. The cache entry is marked VALID. so if you
  167.  * can't fill it, remember to clear it!
  168.  */
  169. int
  170. AllocCache(union sector *s) {
  171.    int set ;
  172.    int oldest ;
  173.    int oldset ;
  174.    int found ;
  175.    int age ;
  176.  
  177.    oldset = 0 ;
  178.    oldest = 0 ;
  179.    found  = 0 ;
  180.  
  181.    for (set = 0; set < SETS; set++) 
  182.       if (cache[set][s->s.line].valid) {
  183.          if (cache[set][s->s.line].key != s->s.key) {
  184.             /*
  185.              * This 'age' calculation is complicated since normally, 
  186.              * AGE = COUNTER - CACHE.AGE, however, if counter has
  187.              * wrapped to zero, such evaluation evaluates ages of < 0 and
  188.              * these entries will never be reselected for reuse.
  189.              *
  190.              * If counter wraps to zero, then CACHE.AGE is generally larger
  191.              * than COUNTER, so we evaluate age as the total of MAXINT-AGE 
  192.              * plus the current value of the counter. IE, how much it took to
  193.              * wrap and reach the current position.
  194.              *
  195.              * Hence, 
  196.              * AGE = ~0 - CACHE.AGE + COUNTER
  197.              */
  198.             age = cache[set][s->s.line].age ;
  199.             if (age > counter)
  200.                age = ((ULONG) ~0 - age) + counter ;
  201.             else
  202.                age = counter - age ;
  203.  
  204.             if (age > oldest)
  205.                oldest = age, oldset = set ;
  206.             }
  207.          else {
  208.             found = 1 ;
  209.             break ; /* key = s.key. Line already in cache */
  210.             }
  211.          }
  212.       else
  213.          break ; /* !valid. Found a free line */
  214.  
  215.    if (found) {
  216.       return -1 ;
  217.       }
  218.  
  219.    if (set == SETS)
  220.       set = oldset ;
  221.  
  222.    cache[set][s->s.line].age = counter ++ ;
  223.    cache[set][s->s.line].key = s->s.key ;
  224.  
  225.    /*
  226.     * If no buffer, allocate one.
  227.     */
  228.    if (! cache[set][s->s.line].buffer )
  229.       if (cache[set][s->s.line].buffer = AllocMem(LINE_SIZE << 9, MEMF_PUBLIC))
  230.          allocnum ++ ;
  231.  
  232.    /*
  233.     * If STILL no buffer, return failure. Otherwise set the VALID flag.
  234.     */
  235.    if (cache[set][s->s.line].buffer )
  236.       cache[set][s->s.line].valid = 1 ;
  237.    else {
  238.       cache[set][s->s.line].valid = 0 ;         /* Allocation failed */
  239.       return -1 ;
  240.       }
  241.  
  242.    return set ;
  243.    }
  244.  
  245. /*
  246.  * Allocate a line of cache and read it from disk.
  247.  */
  248. int
  249. ReadCache(union sector *s) {
  250.    struct MsgPort *port ;
  251.    char *dest ;
  252.    int  set ;
  253.  
  254.    ObtainSemaphore(ss);
  255.  
  256.    port = CreatePort(0,0) ;
  257.  
  258.    if (!port) {
  259.       ReleaseSemaphore(ss) ;
  260.       return -1 ;
  261.       }
  262.  
  263.    if (s->s.offset)
  264.       s->s.offset = 0 ;
  265.  
  266.    IO->io_Message.mn_ReplyPort = port ;
  267.  
  268.    IO->io_Command = CMD_READ;
  269.    IO->io_Offset = s->sector << 9 ;
  270.    IO->io_Length = LINE_SIZE << 9 ;
  271.    IO->io_Data = (APTR) globbuffer ;
  272.  
  273.    oldbeginio(IO,IO->io_Device) ;
  274.    WaitIO((struct IORequest *)IO) ;
  275.  
  276.    DeletePort(port) ;
  277.  
  278.    if ((error=IO->io_Error)) {
  279.       ReleaseSemaphore(ss) ;
  280.       return -1 ;
  281.       }
  282.  
  283.    set = AllocCache(s) ;
  284.  
  285.    if (set < 0) {
  286.       ReleaseSemaphore(ss) ;
  287.       return -1 ;
  288.       }
  289.  
  290.    dest = cache[set][s->s.line].buffer ;
  291.  
  292.    CopyMemQuick(globbuffer, dest, LINE_SIZE << 9) ;
  293.  
  294.    ReleaseSemaphore(ss) ;
  295.    return 0 ;
  296.    }
  297.  
  298. int
  299. ReadBufferToCache(int linestart,int unread,char *buffer) {
  300.  
  301.    union sector s ;
  302.    struct MsgPort *port ;
  303.    int set ;
  304.  
  305.    ObtainSemaphore(ss);
  306.    s.sector = linestart ;
  307.  
  308.    port = CreatePort(0,0) ;
  309.  
  310.    if (!port) {
  311.       ReleaseSemaphore(ss) ;
  312.       return -1 ;
  313.       }
  314.  
  315.    IO->io_Message.mn_ReplyPort = port ;
  316.  
  317.    /*
  318.     * Read enough to fill the buffer.
  319.     */
  320.  
  321.    IO->io_Command = CMD_READ;
  322.    IO->io_Offset = linestart << 9 ;
  323.    IO->io_Length = unread << 9 ;
  324.    IO->io_Data = (APTR) buffer ;
  325.  
  326.    oldbeginio(IO,IO->io_Device) ;
  327.    WaitIO((struct IORequest *)IO) ;
  328.  
  329.    DeletePort(port) ;
  330.  
  331.    if ((error=IO->io_Error)) {
  332.       ReleaseSemaphore(ss) ;
  333.       return -1 ;
  334.       }
  335.  
  336.    while (unread) {
  337.       set = AllocCache(&s) ;
  338.       if (set < 0) {
  339.          ReleaseSemaphore(ss) ;
  340.          return -1 ;
  341.          }
  342.       else {
  343.          CopyMemQuick(buffer,cache[set][s.s.line].buffer, LINE_SIZE << 9) ;
  344.          }
  345.  
  346.       s.sector += LINE_SIZE ;
  347.       unread -= LINE_SIZE ;
  348.       buffer += (LINE_SIZE << 9) ;
  349.       }
  350.  
  351.    ReleaseSemaphore(ss) ;
  352.    return 0 ;
  353.    }
  354.  
  355. /* 
  356.  * This functions checks the cache. If the sector is there, it returns
  357.  * the buffer, otherwise it returns NULL.
  358.  */
  359. char *
  360. FindCache(union sector *s,int set) {
  361.    return & (cache[set][s->s.line].buffer[s->s.offset << 9 ] ) ;
  362.    }
  363.  
  364. /*
  365.  * This function takes 'sector' and set, and decides if the next sector
  366.  * is in the cache.
  367.  */
  368. int
  369. NextEntry(union sector *s, int set) {
  370.    int line ;
  371.  
  372.    line = s->s.line ;
  373.    s->sector ++ ;
  374.  
  375.    if (line == s->s.line) {
  376.       return set ;
  377.       }
  378.    else
  379.       return FindEntry(s) ;
  380.    }
  381.  
  382. /*
  383.  * Search for sector, and mark it invalid.
  384.  */
  385. void
  386. ClearEntry(union sector *s,int set) {
  387.    cache[set][s->s.line].valid = 0 ;
  388.    }
  389.  
  390. CacheUpdate(union sector *s, int seccount, char *buffer) {
  391.    int set ;
  392.  
  393.    ObtainSemaphore(ss) ;
  394.  
  395.    while (seccount) {
  396.       set = FindEntry(s) ;
  397.       if (set >= 0) {
  398.          CopyMemQuick(buffer,FindCache(s,set), ITEM_SIZE) ;
  399.          cache[set][s->s.line].age = counter ++ ;
  400.          }
  401.  
  402.       buffer += ( ITEM_SIZE ) ;
  403.       seccount -- ;
  404.       s->sector ++ ;
  405.       }
  406.  
  407.    ReleaseSemaphore(ss) ;
  408.    return 0 ;
  409.    }
  410.  
  411. void __saveds __asm mybeginio(register __a1 struct IOStdReq *req,
  412.                               register __a6 struct Device *dev) {
  413.  
  414.    union sector s ;
  415.    int   set ;
  416.    int   command ;
  417.    int   secnum ;
  418.    char  *source ;
  419.    char  *buffer ;
  420.  
  421.    int   unread ;
  422.    int   linestart ;
  423.  
  424.    s.sector = req->io_Offset >> 9 ;
  425.    secnum   = req->io_Length >> 9 ;
  426.    command  = req->io_Command ;
  427.    buffer   = (char *) req->io_Data ;
  428.  
  429.    if (command == CMD_WRITE)
  430.       CacheUpdate(&s,secnum,buffer) ;
  431.  
  432.    if (command == CMD_READ) {
  433.       error = 0;
  434.  
  435.       while (secnum) {
  436.          set = FindEntry(&s) ;
  437.          reads++;
  438.  
  439.          if (set < 0) {
  440.             source = NULL ;
  441.             misses++;
  442.             }
  443.          else {
  444.             source = FindCache(&s,set) ;
  445.             hits++;
  446.             }
  447.  
  448.          /*
  449.           * Scan copying buffers to the request.
  450.           */
  451.  
  452.          while (secnum && source) {
  453.             CopyMemQuick(source,buffer,ITEM_SIZE) ;
  454.             buffer += ITEM_SIZE ;
  455.  
  456.             secnum -- ;
  457.             if (secnum) {
  458.                set = NextEntry(&s, set) ;
  459.                if (set < 0) {
  460.                   source = NULL ;
  461.                   }
  462.                else {
  463.                   source = FindCache(&s,set) ;
  464.                   }
  465.                }
  466.             }
  467.  
  468.          if (!secnum) {                                 /* Done ? */
  469.             break ;
  470.             }
  471.  
  472.          /*
  473.           * If we are in the middle of a line, read it in.
  474.           */
  475.          if (s.s.offset) {
  476.             int original = s.sector ;
  477.  
  478.             s.s.offset = 0 ;
  479.             if (ReadCache(&s) < 0) {
  480.                }
  481.  
  482.             s.sector = original ;
  483.             }
  484.          else {
  485.             /*
  486.              * Start scanning at next line.
  487.              */
  488.             unread = 0 ;
  489.  
  490.             linestart = s.sector ; 
  491.  
  492.             /*
  493.              * Scan counting sectors that need reading.
  494.              */
  495.             while ((secnum>unread) && (set < 0)) {
  496.                s.sector = linestart + unread ;
  497.                set = FindEntry(&s) ;
  498.                /*
  499.                 * For efficiency, if a sector is not found, advance to
  500.                 * the next line instead of the next sector.
  501.                 */
  502.                if (set < 0) {
  503.                   unread += LINE_SIZE ;
  504.                   }
  505.                }
  506.  
  507.             if (unread > secnum) {
  508.                unread -= LINE_SIZE ;
  509.                }
  510.  
  511.             if (unread) {
  512.                /*
  513.                 * Read the cache into the supplied buffer, and copy
  514.                 * it to cache memory.
  515.                 */
  516.                ReadBufferToCache(linestart,unread,buffer) ;
  517.                buffer += ( unread << 9 ) ;
  518.                }
  519.              else {
  520.                /*
  521.                 * If there are more sectors, call 'ReadCache()' to get them.
  522.                 */
  523.                ReadCache( (union sector *)&linestart) ;
  524.                }
  525.  
  526.             /*
  527.              * Pick up where we left off.
  528.              */
  529.             s.sector = linestart + unread ;
  530.             secnum -= unread ;
  531.             if (secnum < 0)
  532.                break ;
  533.             }
  534.          }
  535.       /*
  536.        * Done!!!
  537.        */
  538.       }
  539.  
  540.    if (command != CMD_READ)
  541.       oldbeginio(req,dev) ;
  542.    else {
  543.       req->io_Actual = req->io_Length ;
  544.       req->io_Error = error;
  545.       ReplyMsg((struct Message *) req) ;
  546.       } ;
  547.  
  548.    }
  549.  
  550. int
  551. main(int argc, char *argv[]) {
  552.  
  553.    int error ;
  554.    int line,set ;
  555.    int devopen ;    
  556.    struct MsgPort  *port ;
  557.    char *device ;
  558.    int unit ;
  559.    ULONG sigs;
  560.  
  561.    port = NULL;
  562.    devopen = 0 ;
  563.  
  564.    if (argc != 3) {
  565.       printf ("Usage: SmartDisk <yourhd.device> <unitnum>\n") ;
  566.       goto fail ;
  567.       }
  568.    else {
  569.       device = argv[1] ;
  570.       unit = atoi(argv[2]) ;
  571.       }
  572.  
  573.    port = CreatePort(0,0) ;
  574.    IO = (struct IOStdReq *)CreateExtIO(port,sizeof(struct IOStdReq)) ;
  575.  
  576.    error = OpenDevice(device,unit,(struct IORequest *)IO, 0) ;
  577.  
  578.    if (error) {
  579.       printf ("Unable to open %s unit %d\n",device,unit) ;
  580.       goto fail ;
  581.       }
  582.    else
  583.       devopen = 1 ;
  584.  
  585.    ss = AllocMem(sizeof(struct SignalSemaphore), MEMF_PUBLIC) ;
  586.  
  587.    if (!ss)
  588.       goto fail ;
  589.  
  590.    SumLibrary( (struct Library *) IO->io_Device) ; 
  591.  
  592.    InitSemaphore(ss) ;
  593.  
  594.    oldbeginio = SetFunction((struct Library *)IO->io_Device,DEV_BEGINIO,(APTR)mybeginio) ;
  595.  
  596.    for (;;) {
  597.       sigs = Wait (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_F) ;
  598.       if (sigs&SIGBREAKF_CTRL_F) {
  599.          if (reads>0) printf("Reads = %d  Hits = %d  Misses = %d  Percentage = %d\n",reads,hits,misses,(100*hits)/reads);
  600.       }
  601.       else {
  602.          break;
  603.       }
  604.    }
  605.  
  606.    ObtainSemaphore(ss) ;
  607.    SetFunction((struct Library *)IO->io_Device,DEV_BEGINIO,(APTR)oldbeginio) ;
  608.    ReleaseSemaphore(ss) ;
  609.  
  610.  
  611.    for (line = 0; line < LINES; line ++)
  612.       for (set = 0; set < SETS; set ++)
  613.          if (cache[set][line].buffer) {
  614.             FreeMem( cache[set][line].buffer,LINE_SIZE << 9 ) ;
  615.             allocnum -- ;
  616.             }
  617.  
  618.    if (allocnum)
  619.       printf("Allocation mismatch. %d buffers left lying around\n",allocnum) ;
  620.  
  621. fail:
  622.    if (ss)
  623.       FreeMem(ss,sizeof(struct SignalSemaphore)) ;
  624.    if (devopen) {
  625.       IO->io_Message.mn_ReplyPort = port ;
  626.       CloseDevice((struct IORequest *)IO) ;
  627.       }
  628.    if (IO)
  629.       DeleteExtIO((struct IORequest *)IO) ;
  630.    if (port)
  631.       DeletePort(port) ;
  632.  
  633.    return 0 ;
  634.    }
  635.